package ch.trackedbean.binding.guicomponents.action;

import java.awt.event.*;
import java.util.*;
import java.util.concurrent.*;
import javax.swing.*;
import ch.trackedbean.common.*;

/**
 * Abstract implementation of an asynchronous action.<br>
 * This implementation provides the following hooks:<br> {@link #executionNeeded(ActionEvent, List)}<br> {@link #prepare(ActionEvent, List)}<br>
 * {@link #finish(ActionEvent, Object, Object, List)}<br>
 * The work will be executed in {@link #perform(ActionEvent, Object, List)}.<br>
 * If an exception gets thrown it will be passed to the {@link IExceptionHandler} returned by {@link #getExceptionHandler()}.
 * 
 * @author hautle
 * @param <S> The type of the selection
 * @param <P> The type of the prepared object (created by {@link #prepare(ActionEvent, List)})
 * @param <R> The type of the result object (created by {@link #perform(ActionEvent, Object, List)})
 */
public abstract class AbstractAsynchronousAction<S, P, R> extends AbstractSelectionAction<S> {
	/**
	 * Default constructor.
	 */
	public AbstractAsynchronousAction() {
	}

	/**
	 * Default constructor.
	 * 
	 * @param name The name of the action
	 * @param icon The icon of the action
	 */
	public AbstractAsynchronousAction(String name, Icon icon) {
		super(name, icon);
	}

	/**
	 * Default constructor.
	 * 
	 * @param name The name of the action
	 */
	public AbstractAsynchronousAction(String name) {
		super(name);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void actionPerformed(ActionEvent e) {
		try {
			final List<S> selection = getSelection();
			if (!executionNeeded(e, selection))
				return;
			new Worker(e, prepare(e, selection), selection).execute();
		} catch (Exception e1) {
			getExceptionHandler().exceptionOccourd(e1);
		}
	}

	/**
	 * Returns the exception handler to use.<br>
	 * The default implementation just creates and returns an instance of {@link DefaultExceptionHandler}.
	 * 
	 * @return The exception handler
	 */
	protected IExceptionHandler getExceptionHandler() {
		return new DefaultExceptionHandler(getClass());
	}

	/**
	 * This method defines if the asynchronous part has to be executed.<br>
	 * If you return false then the methods {@link #prepare(ActionEvent, List)}, {@link #perform(ActionEvent, Object, List)} and
	 * {@link #finish(ActionEvent,Object, Object, List)} won't be called.
	 * 
	 * @param e The action event
	 * @param selection The selection
	 * @return True if the asynchronous part should be executed
	 * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
	 */
	protected boolean executionNeeded(ActionEvent e, List<S> selection) throws Exception {
		return true;
	}

	/**
	 * Hook method called before the asynchronous part starts.<br>
	 * 
	 * @param e The action event
	 * @param selection The selection
	 * @return The result of the preparation (will be passed to {@link #perform(ActionEvent,Object, List)})
	 * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
	 */
	protected P prepare(ActionEvent e, List<S> selection) throws Exception {
		return null;
	}

	/**
	 * Asynchronous part of the action.<br>
	 * 
	 * @param e The action event
	 * @param prepared The result of {@link #prepare(ActionEvent, List)}
	 * @param selection The selection
	 * @return A result (will be passed to {@link #finish(ActionEvent, Object, Object, List)})
	 * @throws Exception If something went wrong (will be handled by the handler returned by {@link #getExceptionHandler()})
	 */
	public abstract R perform(ActionEvent e, P prepared, List<S> selection) throws Exception;

	/**
	 * Hook method called after the execution of {@link #perform(ActionEvent, Object, List)} (also if the call failed).<br>
	 * Use this method for releasing locks etc.<br>
	 * This method gets executed on the Event Dispatch Thread.
	 * 
	 * @param e The action event
	 * @param prepare The result returned by {@link #prepare(ActionEvent, List)}
	 * @param result The result returned by {@link #perform(ActionEvent, Object, List)} or null if the call failed
	 * @param selection The selection
	 */
	protected void finish(ActionEvent e, P prepare, R result, List<S> selection) {
	}

	/**
	 * The worker executing the asynchronous part.
	 * 
	 * @author hautle
	 */
	protected class Worker extends SwingWorker<R, Void> {
		/** The action event. */
		private final ActionEvent event;

		/** The selection. */
		private final List<S> selection;

		/** The result of {@link AbstractAsynchronousAction#prepare(ActionEvent, List)}. */
		private P prepared;

		/**
		 * Default constructor.
		 * 
		 * @param event The action event
		 * @param prepared The result of {@link AbstractAsynchronousAction#prepare(ActionEvent, List)}.
		 * @param selection The selection
		 */
		protected Worker(ActionEvent event, P prepared, List<S> selection) {
			this.event = event;
			this.selection = selection;
			this.prepared = prepared;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected R doInBackground() throws Exception {
			return perform(event, prepared, selection);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected void done() {
			R res = null;
			try {
				res = get();
			} catch (InterruptedException e) {
				getExceptionHandler().exceptionOccourd(e);
			} catch (ExecutionException e) {
				getExceptionHandler().exceptionOccourd(e.getCause());
			} finally {
				finish(event, prepared, res, selection);
			}
		}
	}
}
